using System;
using System.IO;

namespace DeflateLib
{
    /// <summary>
    /// Helper class for writing bit values, bit blocks in LSB and MSB bits formats.
    /// Doesn't read bytes ahead -- reads next byte if necessary.
    /// </summary>
    class BitReader
    {
        uint readData = 0;
        int startPosition = 0;
        int endPosition = 0;
        long currentPosition = 0;

        internal long CurrentPosition
        {
            get { return currentPosition; }
        }

		internal int InBuffer
		{
			get 
			{
				return endPosition - startPosition;
			}
		}

        private Stream stream;

        internal Stream BaseStream
        {
            get { return stream; }
        }

        internal BitReader(Stream stream)
        {
            this.stream = stream;
        }

        void EnsureData(int bitCount)			
        {
            int readBits = bitCount - InBuffer;
			while (readBits > 0)
			{
				int b = BaseStream.ReadByte();
                
				if (b < 0) throw new DeflateLibException("Unexpected end of stream");
                
				readData |= checked((uint)b << endPosition);
				endPosition += 8;
				readBits -= 8;
			}
        }

        internal bool ReadBit()
        {
            return ReadLSB(1) > 0;
        }

        internal int ReadLSB(int bitCount)
        {
            currentPosition += bitCount;

            EnsureData(bitCount);
                        
			int result = (int)(readData >> startPosition) & ((1 << bitCount) - 1);
			startPosition += bitCount;
			if (endPosition == startPosition)
			{
				endPosition = startPosition = 0;
				readData = 0;
			}
			else if (startPosition >= 8)
			{
				readData >>= startPosition;
				endPosition -= startPosition;
				startPosition = 0;
			}
            
			return result;
        }

        internal int ReadMSB(int bitCount)
        {
            int result = 0;
            for (int i = 0; i < bitCount; i++)
            {
                result <<= 1;
                if (ReadBit()) result |= 1;
            }
            
            return result;
        }

        internal void Align()
        {
            if (endPosition != startPosition)
            {
                currentPosition += endPosition - startPosition;
            }

			endPosition = startPosition = 0;
			readData = 0;
        }

        internal void SetBitPosition(long inputOffset, long initialPosition)
        {
            stream.Position = inputOffset + (initialPosition >> 3);
            endPosition = startPosition = 0;
            readData = 0;
            if ((initialPosition & 7) != 0)
                ReadLSB((int)(initialPosition & 7));

            currentPosition = initialPosition;
        }

        internal void GetBytes(byte[] data, int offset, int count)
        {
            if (endPosition != startPosition)
                throw new InvalidOperationException("Stream is not aligned");

            int read = BaseStream.Read(data, offset, count);

            currentPosition += read << 3;
            if (read != count)
                throw new InvalidOperationException("Not all data read");
        }
    }
}
